/*-------------------------------------------------------------------------
 *
 * functions.c
 *	  SQL语言函数的执行
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/functions.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_func.h"
#include "rewrite/rewriteHandler.h"
#include "storage/proc.h"
#include "tcop/utility.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"


/* 
 * 专门的 DestReceiver，用于在 SQL 函数中收集查询输出 */
typedef struct
{
	DestReceiver pub;			/* 公共已知的函数指针 */
	Tuplestorestate *tstore;	/* 将结果元组放在哪里 */
	MemoryContext cxt;			/* 包含 tstore 的上下文 */
	JunkFilter *filter;			/* 将元组类型转换的过滤器 */
} DR_sqlfunction;

/*
 * 我们为每个查询在一个函数中有一个 execution_state 记录。每个
 * 记录包含其查询的 plantree。如果查询当前处于 F_EXEC_RUN 状态，
 * 那么也有一个 QueryDesc。
 *
 * “next”字段将从单个原始 parsetree 生成的所有 execution_state 记录链在一起。
 * （只有在原始 parsetree 规则扩展的情况下才会有多个。）
 */
typedef enum
{
	F_EXEC_START, F_EXEC_RUN, F_EXEC_DONE
} ExecStatus;

typedef struct execution_state
{
	struct execution_state *next;
	ExecStatus	status;
	bool		setsResult;		/* 如果该查询生成 func 的结果，则为真 */
	bool		lazyEval;		/* 如果应该一次获取一行，则为真 */
	PlannedStmt *stmt;			/* 该查询的计划 */
	QueryDesc  *qd;				/* 除非状态为 RUN，否则为 null */
} execution_state;


/*
 * SQLFunctionCache 记录在第一次调用期间构建，
 * 并从 FmgrInfo 结构的 fn_extra 字段链接。
 *
 * 请注意，目前它仅存在于调用查询的生命周期内。
 * 将来我们应该重写这段代码，使用 plancache.c 保存解析/计划
 * 结果的生命周期。
 *
 * 从物理上讲，这些数据的生命周期与用于调用函数的 FmgrInfo 
 * 一样，存在某些情况（特别是与索引相关）时，FmgrInfo 可能会跨事务存活。
 * 我们不能假定解析/计划树在解析完成的 (子) 事务之后仍然有效，
 * 因此，我们必须用其创建时间的 LXID/subxid 标记记录，
 * 如果它已经过时，则需重新生成所有内容。
 * 为了避免在需要重新生成时的内存泄漏，所有数据保存在
 * FmgrInfo 的 fn_mcxt 的子上下文中。
 */
typedef struct
{
	char	   *fname;			/* 函数名称（用于错误消息） */
	char	   *src;			/* 函数主体文本（用于错误消息） */

	SQLFunctionParseInfoPtr pinfo;	/* 解析器回调钩子的数据显示 */

	Oid			rettype;		/* 实际返回类型 */
	int16		typlen;			/* 返回类型的长度 */
	bool		typbyval;		/* 如果返回类型通过值传递，则为真 */
	bool		returnsSet;		/* 如果返回多行，则为真 */
	bool		returnsTuple;	/* 如果返回整个元组结果，则为真 */
	bool		shutdown_reg;	/* 如果注册了关闭回调，则为真 */
	bool		readonly_func;	/* 在“只读”模式下运行时为真 */
	bool		lazyEval;		/* 如果对结果查询使用 lazyEval，则为真 */

	ParamListInfo paramLI;		/* 表示当前参数的 Param 列表 */

	Tuplestorestate *tstore;	/* 我们在这里累计结果元组 */

	JunkFilter *junkFilter;		/* 如果函数返回 VOID，则为 NULL */

	/*
	 * func_state 是 execution_state 记录的列表，每个记录都是
	 * 它原始 parsetree 的第一个记录，并通过 “next” 字段将任何附加记录链接
	 * 到它。这个子列表结构是必需的，以跟踪原始查询边界的位置。
	 */
	List	   *func_state;

	MemoryContext fcontext;		/* 持有此结构和所有附属数据的内存上下文 */

	LocalTransactionId lxid;	/* 创建缓存的 lxid */
	SubTransactionId subxid;	/* 创建缓存的 subxid */
} SQLFunctionCache;

typedef SQLFunctionCache *SQLFunctionCachePtr;


/* 非导出函数原型 */
static Node *fc_sql_fn_param_ref(ParseState *fc_pstate, ParamRef *fc_pref);
static Node *fc_sql_fn_post_column_ref(ParseState *fc_pstate,
									ColumnRef *fc_cref, Node *fc_var);
static Node *fc_sql_fn_make_param(SQLFunctionParseInfoPtr fc_pinfo,
							   int fc_paramno, int fc_location);
static Node *fc_sql_fn_resolve_param_name(SQLFunctionParseInfoPtr fc_pinfo,
									   const char *fc_paramname, int fc_location);
static List *fc_init_execution_state(List *fc_queryTree_list,
								  SQLFunctionCachePtr fc_fcache,
								  bool fc_lazyEvalOK);
static void fc_init_sql_fcache(FunctionCallInfo fcinfo, Oid fc_collation, bool fc_lazyEvalOK);
static void fc_postquel_start(execution_state *fc_es, SQLFunctionCachePtr fc_fcache);
static bool fc_postquel_getnext(execution_state *fc_es, SQLFunctionCachePtr fc_fcache);
static void fc_postquel_end(execution_state *fc_es);
static void fc_postquel_sub_params(SQLFunctionCachePtr fc_fcache,
								FunctionCallInfo fcinfo);
static Datum fc_postquel_get_single_result(TupleTableSlot *fc_slot,
										FunctionCallInfo fcinfo,
										SQLFunctionCachePtr fc_fcache,
										MemoryContext fc_resultcontext);
static void fc_sql_exec_error_callback(void *fc_arg);
static void fc_ShutdownSQLFunction(Datum fc_arg);
static bool fc_coerce_fn_result_column(TargetEntry *fc_src_tle,
									Oid fc_res_type, int32 fc_res_typmod,
									bool fc_tlist_is_modifiable,
									List **fc_upper_tlist,
									bool *fc_upper_tlist_nontrivial);
static void fc_sqlfunction_startup(DestReceiver *fc_self, int fc_operation, TupleDesc fc_typeinfo);
static bool fc_sqlfunction_receive(TupleTableSlot *fc_slot, DestReceiver *fc_self);
static void fc_sqlfunction_shutdown(DestReceiver *fc_self);
static void fc_sqlfunction_destroy(DestReceiver *fc_self);


/*
 * 为解析 SQL 函数主体准备 SQLFunctionParseInfo 结构
 *
 * 这包括解析多态参数的实际类型。
 *
 * call_expr 可以传递为 NULL，但如果有任何
 * 多态参数，则会失败。
 */
SQLFunctionParseInfoPtr
prepare_sql_fn_parse_info(HeapTuple fc_procedureTuple,
						  Node *fc_call_expr,
						  Oid fc_inputCollation)
{
	SQLFunctionParseInfoPtr fc_pinfo;
	Form_pg_proc fc_procedureStruct = (Form_pg_proc) GETSTRUCT(fc_procedureTuple);
	int			fc_nargs;

	fc_pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));

	/* 仅函数的名称可以用来限定参数名称 */
	fc_pinfo->fname = pstrdup(NameStr(fc_procedureStruct->proname));

	/* 保存函数的输入排序规则 */
	fc_pinfo->collation = fc_inputCollation;

	/*
	 * 从 pg_proc 条目中复制输入参数类型，然后解析任何
	 * 多态类型。
	 */
	fc_pinfo->nargs = fc_nargs = fc_procedureStruct->pronargs;
	if (fc_nargs > 0)
	{
		Oid		   *fc_argOidVect;
		int			fc_argnum;

		fc_argOidVect = (Oid *) palloc(fc_nargs * sizeof(Oid));
		memcpy(fc_argOidVect,
			   fc_procedureStruct->proargtypes.values,
			   fc_nargs * sizeof(Oid));

		for (fc_argnum = 0; fc_argnum < fc_nargs; fc_argnum++)
		{
			Oid			fc_argtype = fc_argOidVect[fc_argnum];

			if (IsPolymorphicType(fc_argtype))
			{
				fc_argtype = get_call_expr_argtype(fc_call_expr, fc_argnum);
				if (fc_argtype == InvalidOid)
					ereport(ERROR,
							(errcode(ERRCODE_DATATYPE_MISMATCH),
							 errmsg("could not determine actual type of argument declared %s",
									format_type_be(fc_argOidVect[fc_argnum]))));
				fc_argOidVect[fc_argnum] = fc_argtype;
			}
		}

		fc_pinfo->argtypes = fc_argOidVect;
	}

	/*
	 * 如果有的话，也收集参数的名称
	 */
	if (fc_nargs > 0)
	{
		Datum		fc_proargnames;
		Datum		fc_proargmodes;
		int			fc_n_arg_names;
		bool		fc_isNull;

		fc_proargnames = SysCacheGetAttr(PROCNAMEARGSNSP, fc_procedureTuple,
									  Anum_pg_proc_proargnames,
									  &fc_isNull);
		if (fc_isNull)
			fc_proargnames = PointerGetDatum(NULL);	/* 只是为了确保 */

		fc_proargmodes = SysCacheGetAttr(PROCNAMEARGSNSP, fc_procedureTuple,
									  Anum_pg_proc_proargmodes,
									  &fc_isNull);
		if (fc_isNull)
			fc_proargmodes = PointerGetDatum(NULL);	/* 只是为了确保 */

		fc_n_arg_names = get_func_input_arg_names(fc_proargnames, fc_proargmodes,
											   &fc_pinfo->argnames);

		/* 偏执：如果数组条目太少，则忽略结果 */
		if (fc_n_arg_names < fc_nargs)
			fc_pinfo->argnames = NULL;
	}
	else
		fc_pinfo->argnames = NULL;

	return fc_pinfo;
}

/* 解析 SQL 函数主体的解析器设置钩子。 */
void sql_fn_parser_setup(struct ParseState *fc_pstate, SQLFunctionParseInfoPtr fc_pinfo)
{
	fc_pstate->p_pre_columnref_hook = NULL;
	fc_pstate->p_post_columnref_hook = fc_sql_fn_post_column_ref;
	fc_pstate->p_paramref_hook = fc_sql_fn_param_ref;
	/* 不需要使用 p_coerce_param_hook */
	fc_pstate->p_ref_hook_state = (void *) fc_pinfo;
}

/* sql_fn_post_column_ref 解析器回调函数，用于 ColumnRefs */
static Node * fc_sql_fn_post_column_ref(ParseState *fc_pstate, ColumnRef *fc_cref, Node *fc_var)
{
	SQLFunctionParseInfoPtr fc_pinfo = (SQLFunctionParseInfoPtr) fc_pstate->p_ref_hook_state;
	int			fc_nnames;
	Node	   *fc_field1;
	Node	   *fc_subfield = NULL;
	const char *fc_name1;
	const char *fc_name2 = NULL;
	Node	   *fc_param;

	/* 永远不要覆盖表列引用。这对应于考虑参数名称出现在单个 SQL 命令之外的作用域中，这正是我们想要的。 */
	if (fc_var != NULL)
		return NULL;

	/*----------
	 * 允许的语法是：
	 *
	 * A		A = 参数名称
	 * A.B		A = 函数名称，B = 参数名称
	 *			或：A = 记录类型的参数名称，B = 字段名称
	 *			（第一个可能性优先）
	 * A.B.C	A = 函数名称，B = 记录类型的参数名称，
	 *			C = 字段名称
	 * A.*		对复合参数 A 的整行引用。
	 * A.B.*	相同，A = 函数名称，B = 参数名称
	 *
	 * 在这里，最后两种情况中的 "*" 可以忽略 --- 主要解析器将负责扩展整行引用。
	 *----------
	 */
	fc_nnames = list_length(fc_cref->fields);

	if (fc_nnames > 3)
		return NULL;

	if (IsA(llast(fc_cref->fields), A_Star))
		fc_nnames--;

	fc_field1 = (Node *) linitial(fc_cref->fields);
	Assert(IsA(fc_field1, String));
	fc_name1 = strVal(fc_field1);
	if (fc_nnames > 1)
	{
		fc_subfield = (Node *) lsecond(fc_cref->fields);
		Assert(IsA(fc_subfield, String));
		fc_name2 = strVal(fc_subfield);
	}

	if (fc_nnames == 3)
	{
		/*
		 * 三部分名称：如果第一部分与函数名称不匹配，
		 * 我们可以立即失败。否则，查找第二部分，
		 * 将第三部分视为字段引用。
		 */
		if (strcmp(fc_name1, fc_pinfo->fname) != 0)
			return NULL;

		fc_param = fc_sql_fn_resolve_param_name(fc_pinfo, fc_name2, fc_cref->location);

		fc_subfield = (Node *) lthird(fc_cref->fields);
		Assert(IsA(fc_subfield, String));
	}
	else if (fc_nnames == 2 && strcmp(fc_name1, fc_pinfo->fname) == 0)
	{
		/*
		 * 第一部分与函数名称匹配的两部分名称：首先查看
		 * 第二部分是否与任何参数名称匹配。
		 */
		fc_param = fc_sql_fn_resolve_param_name(fc_pinfo, fc_name2, fc_cref->location);

		if (fc_param)
		{
			/* 是的，所以这是一个参数引用，没有子字段 */
			fc_subfield = NULL;
		}
		else
		{
			/* 不是，所以尝试匹配为参数名称和子字段 */
			fc_param = fc_sql_fn_resolve_param_name(fc_pinfo, fc_name1, fc_cref->location);
		}
	}
	else
	{
		/* 单一名称，或参数名称后跟子字段 */
		fc_param = fc_sql_fn_resolve_param_name(fc_pinfo, fc_name1, fc_cref->location);
	}

	if (!fc_param)
		return NULL;			/* No match */

	if (fc_subfield)
	{
		/*
		 * 必须是对复合参数字段的引用；否则
		 * ParseFuncOrColumn 将返回 NULL，我们将在
		 * 调用者处失败。
		 */
		fc_param = ParseFuncOrColumn(fc_pstate,
								  list_make1(fc_subfield),
								  list_make1(fc_param),
								  fc_pstate->p_last_srf,
								  NULL,
								  false,
								  fc_cref->location);
	}

	return fc_param;
}

/*
 * sql_fn_param_ref		参数引用的解析回调 ($n 符号)
 */
static Node * fc_sql_fn_param_ref(ParseState *fc_pstate, ParamRef *fc_pref)
{
	SQLFunctionParseInfoPtr fc_pinfo = (SQLFunctionParseInfoPtr) fc_pstate->p_ref_hook_state;
	int			fc_paramno = fc_pref->number;

	/* 检查参数编号是否有效 */
	if (fc_paramno <= 0 || fc_paramno > fc_pinfo->nargs)
		return NULL;			/* 未知的参数编号 */

	return fc_sql_fn_make_param(fc_pinfo, fc_paramno, fc_pref->location);
}

/*
 * sql_fn_make_param		为给定的 paramno 构造一个 Param 节点
 */
static Node * fc_sql_fn_make_param(SQLFunctionParseInfoPtr fc_pinfo,
				  int fc_paramno, int fc_location)
{
	Param	   *fc_param;

	fc_param = makeNode(Param);
	fc_param->paramkind = PARAM_EXTERN;
	fc_param->paramid = fc_paramno;
	fc_param->paramtype = fc_pinfo->argtypes[fc_paramno - 1];
	fc_param->paramtypmod = -1;
	fc_param->paramcollid = get_typcollation(fc_param->paramtype);
	fc_param->location = fc_location;

	/*
	 * 如果我们有一个函数输入排序规则，允许它覆盖
	 * 参数符号的类型衍生排序规则。 （XXX 也许如果类型的排序规则不是默认的，这种情况不应该发生？）
	 */
	if (OidIsValid(fc_pinfo->collation) && OidIsValid(fc_param->paramcollid))
		fc_param->paramcollid = fc_pinfo->collation;

	return (Node *) fc_param;
}

/*
 * 搜索给定名称的函数参数；如果存在，
 * 构造并返回一个 Param 节点。如果没有，则返回 NULL。
 * sql_fn_post_column_ref 的辅助函数。
 */
static Node * fc_sql_fn_resolve_param_name(SQLFunctionParseInfoPtr fc_pinfo,
						  const char *fc_paramname, int fc_location)
{
	int			fc_i;

	if (fc_pinfo->argnames == NULL)
		return NULL;

	for (fc_i = 0; fc_i < fc_pinfo->nargs; fc_i++)
	{
		if (fc_pinfo->argnames[fc_i] && strcmp(fc_pinfo->argnames[fc_i], fc_paramname) == 0)
			return fc_sql_fn_make_param(fc_pinfo, fc_i + 1, fc_location);
	}

	return NULL;
}

/*
 * 为 SQL 函数设置每个查询的 execution_state 记录。
 *
 * 输入是一个解析和重写但未计划的查询树列表的列表。
 * 子列表结构表示原始查询边界。
 */
static List * fc_init_execution_state(List *fc_queryTree_list,
					 SQLFunctionCachePtr fc_fcache,
					 bool fc_lazyEvalOK)
{
	List	   *fc_eslist = NIL;
	execution_state *fc_lasttages = NULL;
	ListCell   *fc_lc1;

	foreach(fc_lc1, fc_queryTree_list)
	{
		List	   *fc_qtlist = lfirst_node(List, fc_lc1);
		execution_state *fc_firstes = NULL;
		execution_state *fc_preves = NULL;
		ListCell   *fc_lc2;

		foreach(fc_lc2, fc_qtlist)
		{
			Query	   *fc_queryTree = lfirst_node(Query, fc_lc2);
			PlannedStmt *fc_stmt;
			execution_state *fc_newes;

			/* 如有需要，计划查询 */
			if (fc_queryTree->commandType == CMD_UTILITY)
			{
				/* 实用程序命令不需要规划。 */
				fc_stmt = makeNode(PlannedStmt);
				fc_stmt->commandType = CMD_UTILITY;
				fc_stmt->canSetTag = fc_queryTree->canSetTag;
				fc_stmt->utilityStmt = fc_queryTree->utilityStmt;
				fc_stmt->stmt_location = fc_queryTree->stmt_location;
				fc_stmt->stmt_len = fc_queryTree->stmt_len;
			}
			else
				fc_stmt = pg_plan_query(fc_queryTree,
									 fc_fcache->src,
									 CURSOR_OPT_PARALLEL_OK,
									 NULL);

			/*
			 * 在函数中预检查所有命令的有效性。这应该
			 * 一般匹配 spi.c 施加的限制。
			 */
			if (fc_stmt->commandType == CMD_UTILITY)
			{
				if (IsA(fc_stmt->utilityStmt, CopyStmt) &&
					((CopyStmt *) fc_stmt->utilityStmt)->filename == NULL)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("cannot COPY to/from client in an SQL function")));

				if (IsA(fc_stmt->utilityStmt, TransactionStmt))
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					/* 翻译者：%s 是一个 SQL 语句名称 */
							 errmsg("%s is not allowed in an SQL function",
									CreateCommandName(fc_stmt->utilityStmt))));
			}

			if (fc_fcache->readonly_func && !CommandIsReadOnly(fc_stmt))
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				/* 翻译者：%s 是一个 SQL 语句名称 */
						 errmsg("%s is not allowed in a non-volatile function",
								CreateCommandName((Node *) fc_stmt))));

			/* 好的，构建此查询的 execution_state */
			fc_newes = (execution_state *) palloc(sizeof(execution_state));
			if (fc_preves)
				fc_preves->next = fc_newes;
			else
				fc_firstes = fc_newes;

			fc_newes->next = NULL;
			fc_newes->status = F_EXEC_START;
			fc_newes->setsResult = false;	/* 下面可能会更改 */
			fc_newes->lazyEval = false;	/* 下面可能会更改 */
			fc_newes->stmt = fc_stmt;
			fc_newes->qd = NULL;

			if (fc_queryTree->canSetTag)
				fc_lasttages = fc_newes;

			fc_preves = fc_newes;
		}

		fc_eslist = lappend(fc_eslist, fc_firstes);
	}

	/*
	 * 将最后一个可以设置标签的查询标记为交付函数结果；
	 * 然后，如果它是简单的 SELECT，则标记为懒惰评估。如果它不是 SELECT，
	 * 我们必须始终将其运行到完成。
	 *
	 * 注意：在某些时候，我们可能会添加额外的标准来决定是否使用
	 * 懒惰评估。然而，我们应该优先在函数
	 * 不返回集合时使用，因为在这种情况下获取多于一行是没有意义的。
	 *
	 * 注意：如果函数返回 VOID，不要设置 setsResult，
	 * 因为没有制作垃圾过滤器。这确保我们将丢弃这类函数中最后语句的任何输出。
	 */
	if (fc_lasttages && fc_fcache->junkFilter)
	{
		fc_lasttages->setsResult = true;
		if (fc_lazyEvalOK &&
			fc_lasttages->stmt->commandType == CMD_SELECT &&
			!fc_lasttages->stmt->hasModifyingCTE)
			fc_fcache->lazyEval = fc_lasttages->lazyEval = true;
	}

	return fc_eslist;
}

/*
 * 为 SQL 函数初始化 SQLFunctionCache
 */
static void fc_init_sql_fcache(FunctionCallInfo fcinfo, Oid fc_collation, bool fc_lazyEvalOK)
{
	FmgrInfo   *fc_finfo = fcinfo->flinfo;
	Oid			fc_foid = fc_finfo->fn_oid;
	MemoryContext fc_fcontext;
	MemoryContext fc_oldcontext;
	Oid			fc_rettype;
	TupleDesc	fc_rettupdesc;
	HeapTuple	fc_procedureTuple;
	Form_pg_proc fc_procedureStruct;
	SQLFunctionCachePtr fc_fcache;
	List	   *fc_queryTree_list;
	List	   *fc_resulttlist;
	ListCell   *fc_lc;
	Datum		fc_tmp;
	bool		fc_isNull;

	/*
	 * 创建一个内存上下文，用于持有所有 SQLFunctionCache 数据。
	 * 它必须是持有 FmgrInfo 的上下文的子上下文。
	 */
	fc_fcontext = AllocSetContextCreate(fc_finfo->fn_mcxt,
									 "SQL function",
									 ALLOCSET_DEFAULT_SIZES);

	fc_oldcontext = MemoryContextSwitchTo(fc_fcontext);

	/*
	 * 创建适当的结构，将其链接到 fcontext 和 fn_extra。
	 * 一旦完成，我们将能够在失败后回收内存，
	 * 即使 FmgrInfo 是长寿命的。
	 */
	fc_fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache));
	fc_fcache->fcontext = fc_fcontext;
	fc_finfo->fn_extra = (void *) fc_fcache;

	/*
	 * 获取与给定函数 Oid 对应的过程元组
	 */
	fc_procedureTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_foid));
	if (!HeapTupleIsValid(fc_procedureTuple))
		elog(ERROR, "cache lookup failed for function %u", fc_foid);
	fc_procedureStruct = (Form_pg_proc) GETSTRUCT(fc_procedureTuple);

	/*
	 * 立即复制函数名称，以供错误报告回调使用，并作为内存上下文标识符
	 */
	fc_fcache->fname = pstrdup(NameStr(fc_procedureStruct->proname));
	MemoryContextSetIdentifier(fc_fcontext, fc_fcache->fname);

	/*
	 * 解析任何多态性，获取实际结果类型和相应的 tupdesc（如果它是行类型的话）。
	 */
	(void) get_call_result_type(fcinfo, &fc_rettype, &fc_rettupdesc);

	fc_fcache->rettype = fc_rettype;

	/* 获取结果类型的 typlen 和 byval 信息 */
	get_typlenbyval(fc_rettype, &fc_fcache->typlen, &fc_fcache->typbyval);

	/* 记住我们是否返回某种 setof */
	fc_fcache->returnsSet = fc_procedureStruct->proretset;

	/* 记住函数是否为 STABLE/IMMUTABLE */
	fc_fcache->readonly_func =
		(fc_procedureStruct->provolatile != PROVOLATILE_VOLATILE);

	/*
	 * 我们需要实际的参数类型以传递给解析器。同时确保参数符号被视为具有函数的已解析输入排序规则。
	 */
	fc_fcache->pinfo = prepare_sql_fn_parse_info(fc_procedureTuple,
											  fc_finfo->fn_expr,
											  fc_collation);

	/*
	 * 当然我们需要函数体文本。
	 */
	fc_tmp = SysCacheGetAttr(PROCOID,
						  fc_procedureTuple,
						  Anum_pg_proc_prosrc,
						  &fc_isNull);
	if (fc_isNull)
		elog(ERROR, "null prosrc for function %u", fc_foid);
	fc_fcache->src = TextDatumGetCString(fc_tmp);

	/* 如果我们有 prosqlbody，关注那个而不是 prosrc。 */
	fc_tmp = SysCacheGetAttr(PROCOID,
						  fc_procedureTuple,
						  Anum_pg_proc_prosqlbody,
						  &fc_isNull);

	/*
	 * 解析并重写函数文本中的查询。使用子列表跟踪原始查询边界。
	 *
	 * 注意：由于解析和规划在 fcontext 中进行，我们将生成大量与 fcache 同存活的杂物。这很烦人，但在模块重写以使用 plancache.c 之前我们将不关心它。
	 */
	fc_queryTree_list = NIL;
	if (!fc_isNull)
	{
		Node	   *fc_n;
		List	   *fc_stored_query_list;

		fc_n = stringToNode(TextDatumGetCString(fc_tmp));
		if (IsA(fc_n, List))
			fc_stored_query_list = linitial_node(List, castNode(List, fc_n));
		else
			fc_stored_query_list = list_make1(fc_n);

		foreach(fc_lc, fc_stored_query_list)
		{
			Query	   *fc_parsetree = lfirst_node(Query, fc_lc);
			List	   *fc_queryTree_sublist;

			AcquireRewriteLocks(fc_parsetree, true, false);
			fc_queryTree_sublist = pg_rewrite_query(fc_parsetree);
			fc_queryTree_list = lappend(fc_queryTree_list, fc_queryTree_sublist);
		}
	}
	else
	{
		List	   *fc_raw_parsetree_list;

		fc_raw_parsetree_list = pg_parse_query(fc_fcache->src);

		foreach(fc_lc, fc_raw_parsetree_list)
		{
			RawStmt    *fc_parsetree = lfirst_node(RawStmt, fc_lc);
			List	   *fc_queryTree_sublist;

			fc_queryTree_sublist = pg_analyze_and_rewrite_withcb(fc_parsetree,
															  fc_fcache->src,
															  (ParserSetupHook) sql_fn_parser_setup,
															  fc_fcache->pinfo,
															  NULL);
			fc_queryTree_list = lappend(fc_queryTree_list, fc_queryTree_sublist);
		}
	}

	/*
	 * 检查是否没有我们不想允许的语句。
	 */
	check_sql_fn_statements(fc_queryTree_list);

	/*
	 * 检查函数返回它声称的类型。虽然在简单情况下在定义函数时已经完成，但我们仍需重新检查，因为在函数查询中使用的数据库对象可能已更改类型。如果函数有任何多态参数，我们无论如何都需要重新检查。此外，check_sql_fn_retval 处理注入任何必要的列类型强制转换。（但我们不会要求它插入被删除列的空值；junkfilter 处理这个问题。）
	 *
	 * 注意：我们根据是否返回整个元组结果或仅单列清除 fcache->returnsTuple。在后者情况下，我们清除 returnsTuple，因为我们不需要与标量结果情况有不同的处理，即使它是行类型列。（然而，在这种情况下我们必须强制懒惰评估模式；否则我们需要额外的代码将行类型列扩展为多个列，因为我们无法通知调用者应该这样做。）
	 */
	fc_fcache->returnsTuple = check_sql_fn_retval_ext(fc_queryTree_list,
												   fc_rettype,
												   fc_rettupdesc,
												   fc_procedureStruct->prokind,
												   false,
												   &fc_resulttlist);

	/*
	 * 构造一个 JunkFilter，我们可以用来强制返回的行类型转换为所需形式，除非结果类型是 VOID，在这种情况下没有什么可以强制转换的。（XXX 如果常常，JunkFilter没有做什么非常有趣的事情，但这个模块的许多部分无论如何认为它应该存在。）
	 */
	if (fc_rettype != VOIDOID)
	{
		TupleTableSlot *fc_slot = MakeSingleTupleTableSlot(NULL,
														&TTSOpsMinimalTuple);

		/*
		 * 如果结果是复合的，*并且*我们返回整个元组结果，我们需要为任何被删除的列插入空值。在单列结果情况下，复合列值中可能有被删除的列，但这不是我们的问题。在结果列表中不应有 resjunk 条目，因此在第二种情况下 JunkFilter 当然是一个无操作。
		 */
		if (fc_rettupdesc && fc_fcache->returnsTuple)
			fc_fcache->junkFilter = ExecInitJunkFilterConversion(fc_resulttlist,
															  fc_rettupdesc,
															  fc_slot);
		else
			fc_fcache->junkFilter = ExecInitJunkFilter(fc_resulttlist, fc_slot);
	}

	if (fc_fcache->returnsTuple)
	{
		/* 确保输出行类型得到正确的祝福 */
		BlessTupleDesc(fc_fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor);
	}
	else if (fc_fcache->returnsSet && type_is_rowtype(fc_fcache->rettype))
	{
		/*
		 * 将行类型作为标量返回 --- materialize 将不工作。现在覆盖任何调用者对 materialize 模式的偏好已经足够，但为此在 init_execution_state 中添加更多智能，我们可能需要一个三态标志，而不是布尔值。
		 */
		fc_lazyEvalOK = true;
	}

	/* 最后，规划查询 */
	fc_fcache->func_state = fc_init_execution_state(fc_queryTree_list,
											  fc_fcache,
											  fc_lazyEvalOK);

	/* 用创建时间标记 fcache，以表明它是有效的 */
	fc_fcache->lxid = MyProc->lxid;
	fc_fcache->subxid = GetCurrentSubTransactionId();

	ReleaseSysCache(fc_procedureTuple);

	MemoryContextSwitchTo(fc_oldcontext);
}

/* 启动一个执行状态节点的执行 */
static void fc_postquel_start(execution_state *fc_es, SQLFunctionCachePtr fc_fcache)
{
	DestReceiver *fc_dest;

	Assert(fc_es->qd == NULL);

	/* 调用者应确保一个合适的快照处于活动状态 */
	Assert(ActiveSnapshotSet());

	/*
	 * 如果这个查询生成函数结果，将其输出发送到 tuplestore；否则，丢弃任何输出。
	 */
	if (fc_es->setsResult)
	{
		DR_sqlfunction *fc_myState;

		fc_dest = CreateDestReceiver(DestSQLFunction);
		/* 将所需信息传递给目标接收器例程 */
		fc_myState = (DR_sqlfunction *) fc_dest;
		Assert(fc_myState->pub.mydest == DestSQLFunction);
		fc_myState->tstore = fc_fcache->tstore;
		fc_myState->cxt = CurrentMemoryContext;
		fc_myState->filter = fc_fcache->junkFilter;
	}
	else
		fc_dest = None_Receiver;

	fc_es->qd = CreateQueryDesc(fc_es->stmt,
							 fc_fcache->src,
							 GetActiveSnapshot(),
							 InvalidSnapshot,
							 fc_dest,
							 fc_fcache->paramLI,
							 fc_es->qd ? fc_es->qd->queryEnv : NULL,
							 0);

	/* 公用命令不需要执行器。 */
	if (fc_es->qd->operation != CMD_UTILITY)
	{
		/*
		 * 在 lazyEval 模式下，不让执行器设置 AfterTrigger 上下文。这不仅是一个优化，因为我们不能在函数执行退出时仍然存在堆叠的 AfterTrigger 层。我们小心不为任何可能排队触发器的语句选择 lazyEval 模式。
		 */
		int			fc_eflags;

		if (fc_es->lazyEval)
			fc_eflags = EXEC_FLAG_SKIP_TRIGGERS;
		else
			fc_eflags = 0;			/* 默认完成标志 */
		ExecutorStart(fc_es->qd, fc_eflags);
	}

	fc_es->status = F_EXEC_RUN;
}

/* 运行一个执行状态；直到完成或到达第一个结果行 */
/* 如果我们运行到完成，返回真 */
static bool fc_postquel_getnext(execution_state *fc_es, SQLFunctionCachePtr fc_fcache)
{
	bool		fc_result;

	if (fc_es->qd->operation == CMD_UTILITY)
	{
		ProcessUtility(fc_es->qd->plannedstmt,
					   fc_fcache->src,
					   true,	/* 保护函数缓存的解析树 */
					   PROCESS_UTILITY_QUERY,
					   fc_es->qd->params,
					   fc_es->qd->queryEnv,
					   fc_es->qd->dest,
					   NULL);
		fc_result = true;			/* 永远不会提前停止 */
	}
	else
	{
		/* 除非 lazyEval，否则运行常规命令直到完成 */
		uint64		fc_count = (fc_es->lazyEval) ? 1 : 0;

		ExecutorRun(fc_es->qd, ForwardScanDirection, fc_count, !fc_fcache->returnsSet || !fc_es->lazyEval);

		/*
		 * 如果我们请求运行到完成或没有元组返回，则命令必须是完整的。
		 */
		fc_result = (fc_count == 0 || fc_es->qd->estate->es_processed == 0);
	}

	return fc_result;
}

/* 关闭一个执行状态节点的执行 */
static void fc_postquel_end(execution_state *fc_es)
{
	/* 标记状态为完成，以确保我们不会两次执行 ExecutorEnd */
	fc_es->status = F_EXEC_DONE;

	/* 公用命令不需要执行器。 */
	if (fc_es->qd->operation != CMD_UTILITY)
	{
		ExecutorFinish(fc_es->qd);
		ExecutorEnd(fc_es->qd);
	}

	fc_es->qd->dest->rDestroy(fc_es->qd->dest);

	FreeQueryDesc(fc_es->qd);
	fc_es->qd = NULL;
}

 /* 构建 ParamListInfo 数组以表示当前参数 */
static void fc_postquel_sub_params(SQLFunctionCachePtr fc_fcache,
					FunctionCallInfo fcinfo)
{
	int			fc_nargs = fcinfo->nargs;

	if (fc_nargs > 0)
	{
		ParamListInfo fc_paramLI;
		Oid		   *fc_argtypes = fc_fcache->pinfo->argtypes;

		if (fc_fcache->paramLI == NULL)
		{
			fc_paramLI = makeParamList(fc_nargs);
			fc_fcache->paramLI = fc_paramLI;
		}
		else
		{
			fc_paramLI = fc_fcache->paramLI;
			Assert(fc_paramLI->numParams == fc_nargs);
		}

		for (int fc_i = 0; fc_i < fc_nargs; fc_i++)
		{
			ParamExternData *fc_prm = &fc_paramLI->params[fc_i];

			 /*
			 * 如果传入的参数值是可读写扩展数据，我们
			 * 强制将其设置为只读。我们完全有权在其上自由操作，
			 * 但问题是如果参数在函数中被引用多次，
			 * 之前的引用可能会变更后续引用看到的值，这样是不行的。
			 * 如果我们能确定函数计划中的 Param 节点数量，
			 * 那我们就可以做得更好；但我们可能尚未计划
			 * 所有语句，也没有计划树遍历基础设施。
			 * （检查解析树并不够好，
			 * 因为在规划过程中可能会发生函数内联。）
			 */
			fc_prm->isnull = fcinfo->args[fc_i].isnull;
			fc_prm->value = MakeExpandedObjectReadOnly(fcinfo->args[fc_i].value,
													fc_prm->isnull,
													get_typlen(fc_argtypes[fc_i]));
			fc_prm->pflags = 0;
			fc_prm->ptype = fc_argtypes[fc_i];
		}
	}
	else
		fc_fcache->paramLI = NULL;
}

 /*
 * 从单个结果行中提取 SQL 函数的值。这适用于
 * 标量（非集合）函数和懒评估集合结果的每一行。
 */
static Datum fc_postquel_get_single_result(TupleTableSlot *fc_slot,
						   FunctionCallInfo fcinfo,
						   SQLFunctionCachePtr fc_fcache,
						   MemoryContext fc_resultcontext)
{
	Datum		fc_value;
	MemoryContext fc_oldcontext;

	 /*
	 * 设置以返回函数值。对于按引用传递的数据类型，
	 * 确保在 resultcontext 中分配结果，而不是当前内存
	 * 上下文（其具有查询生命周期）。我们不能将数据留在
	 * TupleTableSlot 中，因为我们打算在返回之前清除该槽。
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_resultcontext);

	if (fc_fcache->returnsTuple)
	{
		/* 我们必须将整个元组作为Datum返回。 */
		fcinfo->isnull = false;
		fc_value = ExecFetchSlotHeapTupleDatum(fc_slot);
	}
	else
	{
		 /*
		 * 返回一个标量，我们必须从 SELECT 结果的第一列提取它，
		 * 然后根据需要复制到结果上下文中。
		 */
		fc_value = slot_getattr(fc_slot, 1, &(fcinfo->isnull));

		if (!fcinfo->isnull)
			fc_value = datumCopy(fc_value, fc_fcache->typbyval, fc_fcache->typlen);
	}

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_value;
}

 /*
 * fmgr_sql：用于 SQL 函数的函数调用管理器
 */
Datum fmgr_sql(PG_FUNCTION_ARGS)
{
	SQLFunctionCachePtr fc_fcache;
	ErrorContextCallback fc_sqlerrcontext;
	MemoryContext fc_oldcontext;
	bool		fc_randomAccess;
	bool		fc_lazyEvalOK;
	bool		fc_is_first;
	bool		fc_pushed_snapshot;
	execution_state *fc_es;
	TupleTableSlot *fc_slot;
	Datum		fc_result;
	List	   *fc_eslist;
	ListCell   *fc_eslc;

	/*
	 * 为 ereport() 设置错误回溯支持。
	 */
	fc_sqlerrcontext.callback = fc_sql_exec_error_callback;
	fc_sqlerrcontext.arg = fcinfo->flinfo;
	fc_sqlerrcontext.previous = error_context_stack;
	error_context_stack = &fc_sqlerrcontext;

	 /* 检查调用上下文 */
	if (fcinfo->flinfo->fn_retset)
	{
		ReturnSetInfo *fc_rsi = (ReturnSetInfo *) fcinfo->resultinfo;

		 /*
		 * 为了简化，我们要求调用者支持两种集合评估模式。
		 * 有些情况下我们必须使用一种或必须使用另一种，
		 * 直到我们知道再推迟检查真的不值得。但
		 * 请注意，我们并不要求调用者提供 expectedDesc。
		 */
		if (!fc_rsi || !IsA(fc_rsi, ReturnSetInfo) ||
			(fc_rsi->allowedModes & SFRM_ValuePerCall) == 0 ||
			(fc_rsi->allowedModes & SFRM_Materialize) == 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("set-valued function called in context that cannot accept a set")));
		fc_randomAccess = fc_rsi->allowedModes & SFRM_Materialize_Random;
		fc_lazyEvalOK = !(fc_rsi->allowedModes & SFRM_Materialize_Preferred);
	}
	else
	{
		fc_randomAccess = false;
		fc_lazyEvalOK = true;
	}

	 /*
	 * 如果是第一次通过，则初始化 fcache（构建计划）；
	 * 如果缓存失效，则重新初始化。
	 */
	fc_fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;

	if (fc_fcache != NULL)
	{
		if (fc_fcache->lxid != MyProc->lxid ||
			!SubTransactionIsActive(fc_fcache->subxid))
		{
			 /* 它已经失效；解除连接并删除 */
			fcinfo->flinfo->fn_extra = NULL;
			MemoryContextDelete(fc_fcache->fcontext);
			fc_fcache = NULL;
		}
	}

	if (fc_fcache == NULL)
	{
		fc_init_sql_fcache(fcinfo, PG_GET_COLLATION(), fc_lazyEvalOK);
		fc_fcache = (SQLFunctionCachePtr) fcinfo->flinfo->fn_extra;
	}

	 /*
	 * 切换到 fcache 存在的上下文。这确保我们的
	 * tuplestore 等将具有足够的生命周期。子执行器
	 * 负责删除每个元组的信息。（XXX 在长生命周期的 FmgrInfo 的情况下，
	 * 该策略导致更多的内存泄漏，但并不完全清楚该将内容保留在哪里。）
	 */
	fc_oldcontext = MemoryContextSwitchTo(fc_fcache->fcontext);

	 /*
	 * 查找函数中第一个未完成的查询，并注意它是否是
	 * 第一个查询。
	 */
	fc_eslist = fc_fcache->func_state;
	fc_es = NULL;
	fc_is_first = true;
	foreach(fc_eslc, fc_eslist)
	{
		fc_es = (execution_state *) lfirst(fc_eslc);

		while (fc_es && fc_es->status == F_EXEC_DONE)
		{
			fc_is_first = false;
			fc_es = fc_es->next;
		}

		if (fc_es)
			break;
	}

	 /*
	 * 如果开始新的执行，则将参数转换为适当的格式。
	 * （如果继续执行，我们可以重新使用先前的参数。）
	 */
	if (fc_is_first && fc_es && fc_es->status == F_EXEC_START)
		fc_postquel_sub_params(fc_fcache, fcinfo);

	 /*
	 * 如果尚未拥有，则构建 tuplestore 来保存结果。
	 * 注意它是在查询生命周期上下文中。
	 */
	if (!fc_fcache->tstore)
		fc_fcache->tstore = tuplestore_begin_heap(fc_randomAccess, false, work_mem);

	 /*
	 * 逐个执行函数中的每个命令，直到我们要么
	 * 用尽命令，要么从懒惰计算的 SELECT 中获得结果行。
	 *
	 * 有关快照管理的说明：
	 *
	 * 在只读函数中，我们只使用周围查询的快照。
	 *
	 * 在非只读函数中，我们依赖于这样一个事实，即
	 * 我们在函数的查询之间永远不会挂起执行：在完成之前
	 * 挂起执行的唯一原因是我们正在从一个
	 * 懒惰评估的 SELECT 返回一行。因此，当首次进入此循环时，
	 * 我们要么启动一个新查询（并推送一个新的快照），
	 * 要么从现有的查询描述符重新建立活动快照。如果我们需要
	 * 在随后循环的执行中启动新查询，或者我们需要
	 * 一个新的快照（并且 pushed_snapshot 为 false），
	 * 或者现有的快照在活动堆栈上，我们就可以增加其命令 ID。
	 */
	fc_pushed_snapshot = false;
	while (fc_es)
	{
		bool		fc_completed;

		if (fc_es->status == F_EXEC_START)
		{
			 /*
			 * 如果不是只读，确保在每个命令中前进命令计数器，
			 * 以便在此事务中到目前为止的所有工作都是
			 * 可见的。如果还没有快照，则获取一个新的快照，
			 * 否则在现有快照中只需增加命令 ID。
			 */
			if (!fc_fcache->readonly_func)
			{
				CommandCounterIncrement();
				if (!fc_pushed_snapshot)
				{
					PushActiveSnapshot(GetTransactionSnapshot());
					fc_pushed_snapshot = true;
				}
				else
					UpdateActiveSnapshotCommandId();
			}

			fc_postquel_start(fc_es, fc_fcache);
		}
		else if (!fc_fcache->readonly_func && !fc_pushed_snapshot)
		{
			 /* 重新建立活动快照时重新进入函数 */
			PushActiveSnapshot(fc_es->qd->snapshot);
			fc_pushed_snapshot = true;
		}

		fc_completed = fc_postquel_getnext(fc_es, fc_fcache);

		/*
		 * 如果我们已完成命令的执行，现在可以关闭它。我们需要返回的任何行都安全地存储在tuplestore中，并且我们希望确保，例如，在返回任何内容之前触发AFTER触发器。此外，如果函数不返回集，我们也可以关闭它，因为它必须是SELECT，并且我们不关心获取更多结果行。
		 */
		if (fc_completed || !fc_fcache->returnsSet)
			fc_postquel_end(fc_es);

		/*
		 * 如果我们没有关闭，则退出循环（这意味着我们得到了一个懒评估的行）。否则，我们将继续执行整个函数，依赖于tuplestore来保持数据以最终返回。这是必要的，因为设置结果的INSERT/UPDATE/DELETE RETURNING可能会随之有额外的规则插入命令，我们希望在返回任何内容之前完成所有这些命令。
		 */
		if (fc_es->status != F_EXEC_DONE)
			break;

		/*
		 * 向下一个执行状态推进，可能在下一个列表中。
		 */
		fc_es = fc_es->next;
		while (!fc_es)
		{
			fc_eslc = lnext(fc_eslist, fc_eslc);
			if (!fc_eslc)
				break;			/* 函数结束 */

			fc_es = (execution_state *) lfirst(fc_eslc);

			/*
			 * 刷新当前快照，以便我们将为新的查询列表获取新的快照。这确保在原始查询边界处拍摄新快照，与交互式执行的行为相匹配。
			 */
			if (fc_pushed_snapshot)
			{
				PopActiveSnapshot();
				fc_pushed_snapshot = false;
			}
		}
	}

	/*
	 * 现在tuplestore包含了我们应该返回的任何行。
	 */
	if (fc_fcache->returnsSet)
	{
		ReturnSetInfo *fc_rsi = (ReturnSetInfo *) fcinfo->resultinfo;

		if (fc_es)
		{
			/*
			 * 如果我们没有完成，肯定有一个懒评估行。
			 */
			Assert(fc_es->lazyEval);
			/* 重新使用junkfilter的输出槽以获取元组 */
			Assert(fc_fcache->junkFilter);
			fc_slot = fc_fcache->junkFilter->jf_resultSlot;
			if (!tuplestore_gettupleslot(fc_fcache->tstore, true, false, fc_slot))
				elog(ERROR, "failed to fetch lazy-eval tuple");
			/* 将结果提取为数据，并从槽中复制出来 */
			fc_result = fc_postquel_get_single_result(fc_slot, fcinfo,
												fc_fcache, fc_oldcontext);
			/* 清空tuplestore，但为下次保留它 */
			/* 注意：这可能会删除槽的内容，但我们不在乎 */
			tuplestore_clear(fc_fcache->tstore);

			/*
			 * 让调用者知道我们还没有完成。
			 */
			fc_rsi->isDone = ExprMultipleResult;

			/*
			 * 确保如果 exprcontext 没有完全运行，我们将会干净地关闭。
			 */
			if (!fc_fcache->shutdown_reg)
			{
				RegisterExprContextCallback(fc_rsi->econtext,
											fc_ShutdownSQLFunction,
											PointerGetDatum(fc_fcache));
				fc_fcache->shutdown_reg = true;
			}
		}
		else if (fc_fcache->lazyEval)
		{
			/*
			 * 我们已经完成了延迟评估。清理。
			 */
			tuplestore_clear(fc_fcache->tstore);

			/*
			 * 让调用者知道我们已经完成。
			 */
			fc_rsi->isDone = ExprEndResult;

			fcinfo->isnull = true;
			fc_result = (Datum) 0;

			/* 如果我们创建了关闭回调，则注销该回调 */
			if (fc_fcache->shutdown_reg)
			{
				UnregisterExprContextCallback(fc_rsi->econtext,
											  fc_ShutdownSQLFunction,
											  PointerGetDatum(fc_fcache));
				fc_fcache->shutdown_reg = false;
			}
		}
		else
		{
			/*
			 * 我们已经完成了非延迟评估。返回 tuplestore 中的内容。
			 * （现在调用者有责任在完成时释放 tuplestore。）
			 */
			fc_rsi->returnMode = SFRM_Materialize;
			fc_rsi->setResult = fc_fcache->tstore;
			fc_fcache->tstore = NULL;
			/* 必须复制描述，因为 execSRF.c 将释放它 */
			if (fc_fcache->junkFilter)
				fc_rsi->setDesc = CreateTupleDescCopy(fc_fcache->junkFilter->jf_cleanTupType);

			fcinfo->isnull = true;
			fc_result = (Datum) 0;

			/* 如果我们创建了关闭回调，则注销该回调 */
			if (fc_fcache->shutdown_reg)
			{
				UnregisterExprContextCallback(fc_rsi->econtext,
											  fc_ShutdownSQLFunction,
											  PointerGetDatum(fc_fcache));
				fc_fcache->shutdown_reg = false;
			}
		}
	}
	else
	{
		/*
		 * 非集合函数。如果我们得到一行，返回它；否则返回 NULL。
		 */
		if (fc_fcache->junkFilter)
		{
			/* 重新使用junkfilter的输出槽以获取元组 */
			fc_slot = fc_fcache->junkFilter->jf_resultSlot;
			if (tuplestore_gettupleslot(fc_fcache->tstore, true, false, fc_slot))
				fc_result = fc_postquel_get_single_result(fc_slot, fcinfo,
													fc_fcache, fc_oldcontext);
			else
			{
				fcinfo->isnull = true;
				fc_result = (Datum) 0;
			}
		}
		else
		{
			/* 仅在 VOID 函数和过程时才能到达此处 */
			Assert(fc_fcache->rettype == VOIDOID);
			fcinfo->isnull = true;
			fc_result = (Datum) 0;
		}

		/* 清空tuplestore，但为下次保留它 */
		tuplestore_clear(fc_fcache->tstore);
	}

	/* 如果我们已经推送了快照，则弹出快照 */
	if (fc_pushed_snapshot)
		PopActiveSnapshot();

	/*
	 * 如果我们已经遍历了函数中的每个命令，我们完成了。重置
	 * 执行状态以便在下次调用时重新开始。
	 */
	if (fc_es == NULL)
	{
		foreach(fc_eslc, fc_fcache->func_state)
		{
			fc_es = (execution_state *) lfirst(fc_eslc);
			while (fc_es)
			{
				fc_es->status = F_EXEC_START;
				fc_es = fc_es->next;
			}
		}
	}

	error_context_stack = fc_sqlerrcontext.previous;

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_result;
}


/*
 * 错误上下文回调，以便让我们提供调用栈回溯
 */
static void fc_sql_exec_error_callback(void *fc_arg)
{
	FmgrInfo   *fc_flinfo = (FmgrInfo *) fc_arg;
	SQLFunctionCachePtr fc_fcache = (SQLFunctionCachePtr) fc_flinfo->fn_extra;
	int			fc_syntaxerrposition;

	/*
	 * 如果 init_sql_fcache() 没有保存函数名称，则我们无法做任何有效的事情。
	 */
	if (fc_fcache == NULL || fc_fcache->fname == NULL)
		return;

	/*
	 * 如果有语法错误位置，将其转换为内部语法错误。
	 */
	fc_syntaxerrposition = geterrposition();
	if (fc_syntaxerrposition > 0 && fc_fcache->src != NULL)
	{
		errposition(0);
		internalerrposition(fc_syntaxerrposition);
		internalerrquery(fc_fcache->src);
	}

	/*
	 * 尝试确定我们在函数中失败的位置。如果有一个非空的 QueryDesc 查询，指出它。
	 * （我们检查这个，而不是查看 F_EXEC_RUN 状态，以便在 ExecutorStart 或
	 * ExecutorEnd 期间的错误可以归咎于适当的查询；参见 postquel_start 和
	 * postquel_end。）
	 */
	if (fc_fcache->func_state)
	{
		execution_state *fc_es;
		int			fc_query_num;
		ListCell   *fc_lc;

		fc_es = NULL;
		fc_query_num = 1;
		foreach(fc_lc, fc_fcache->func_state)
		{
			fc_es = (execution_state *) lfirst(fc_lc);
			while (fc_es)
			{
				if (fc_es->qd)
				{
					errcontext("SQL function \"%s\" statement %d",
							   fc_fcache->fname, fc_query_num);
					break;
				}
				fc_es = fc_es->next;
			}
			if (fc_es)
				break;
			fc_query_num++;
		}
		if (fc_es == NULL)
		{
			/*
			 * 无法识别正在运行的查询；可能是函数入口、
			 * 函数出口，或者查询之间。
			 */
			errcontext("SQL function \"%s\"", fc_fcache->fname);
		}
	}
	else
	{
		/*
		 * 假设我们在 init_sql_fcache() 中失败。（函数实际上可能有一个空体，
		 * 但在这种情况下，我们可以将所有错误报告为“在启动期间”。）
		 */
		errcontext("SQL function \"%s\" during startup", fc_fcache->fname);
	}
}


/*
 * 如果需要在函数返回集运行完成之前关闭，则回调函数。
 */
static void fc_ShutdownSQLFunction(Datum fc_arg)
{
	SQLFunctionCachePtr fc_fcache = (SQLFunctionCachePtr) DatumGetPointer(fc_arg);
	execution_state *fc_es;
	ListCell   *fc_lc;

	foreach(fc_lc, fc_fcache->func_state)
	{
		fc_es = (execution_state *) lfirst(fc_lc);
		while (fc_es)
		{
			/* 关闭任何仍在运行的内容 */
			if (fc_es->status == F_EXEC_RUN)
			{
				/* 为任何被调用的函数重新建立活动快照 */
				if (!fc_fcache->readonly_func)
					PushActiveSnapshot(fc_es->qd->snapshot);

				fc_postquel_end(fc_es);

				if (!fc_fcache->readonly_func)
					PopActiveSnapshot();
			}

			/* 在我们再次被调用的情况下重置状态为 START */
			fc_es->status = F_EXEC_START;
			fc_es = fc_es->next;
		}
	}

	/* 如果我们有 tuplestore，则释放它 */
	if (fc_fcache->tstore)
		tuplestore_end(fc_fcache->tstore);
	fc_fcache->tstore = NULL;

	/* execUtils 将注销回调... */
	fc_fcache->shutdown_reg = false;
}

/*
 * check_sql_fn_statements
 *
 * 检查 SQL 函数中的语句。如果有任何不接受的内容，将出错。
 */
void check_sql_fn_statements(List *fc_queryTreeLists)
{
	ListCell   *fc_lc;

	/* 我们被提供了一个查询子列表的列表 */
	foreach(fc_lc, fc_queryTreeLists)
	{
		List	   *fc_sublist = lfirst_node(List, fc_lc);
		ListCell   *fc_lc2;

		foreach(fc_lc2, fc_sublist)
		{
			Query	   *fc_query = lfirst_node(Query, fc_lc2);

			/*
			 * 不允许调用带有输出参数的过程。当前的
			 * 实现会直接丢弃输出值，除非该语句是最后一个。
			 * 根据 SQL 标准，我们应该按名称分配输出值。
			 * 通过在这里禁止这种情况，我们保留了未来改进的机会。
			 */
			if (fc_query->commandType == CMD_UTILITY &&
				IsA(fc_query->utilityStmt, CallStmt))
			{
				CallStmt   *fc_stmt = (CallStmt *) fc_query->utilityStmt;

				if (fc_stmt->outargs != NIL)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("calling procedures with output arguments is not supported in SQL functions")));
			}
		}
	}
}

/*
 * check_sql_fn_retval()
 *		检查一系列 SQL 解析树的返回值。
 *
 * SQL 函数的返回值是函数中最后一个可设置标签查询返回的值。
 * 我们在这里进行了一些临时的类型检查和强制转换，以确保
 * 函数返回其应返回的内容。
 * 请注意，我们可能会实际修改最后的查询以使其匹配！
 *
 * 如果 SQL 函数返回其最后语句的整个元组结果，则此函数返回 true，
 * 如果只返回该语句的第一列结果，则返回 false。如果最后的语句
 * 完全没有返回正确的类型，则会抛出错误。
 *
 * 请注意，由于我们允许 "SELECT rowtype_expression"，即使声明的函数
 * 返回类型是 rowtype，结果也可能为 false。
 *
 * 对于多态函数，传递的 rettype 必须是函数的实际解析输出类型。
 * （这意味着我们无法在多态函数的函数定义期间检查类型。）如果我们
 * 看到多态的 rettype，我们将抛出错误，说明它不是受支持的 rettype。
 *
 * 如果函数返回复合类型，传递的 rettupdesc 应该描述预期的输出。
 * 如果 rettupdesc 为 NULL，我们无法验证输出是否匹配；这应该只在
 * fmgr_sql_validator() 中发生，或者当函数返回 RECORD 且调用者
 * 实际上不关心是什么复合类型时。
 *
 * （通常，rettype 和 rettupdesc 是通过 get_call_result_type
 * 或兄弟函数计算的。）
 *
 * 除了强制单个输出列外，我们还可以修改输出，以包括任何在 rettupdesc 中
 * 出现的已删除列的虚拟 NULL 列。这仅在调用者要求时执行。
 *
 * 如果 resultTargetList 不是 NULL，则 *resultTargetList 设置为定义
 * 最终语句结果的目标列表。例外情况：如果函数被定义为返回 VOID，
 * 则 *resultTargetList 设置为 NIL。
 */
bool check_sql_fn_retval(List *fc_queryTreeLists,
					Oid fc_rettype, TupleDesc fc_rettupdesc,
					bool fc_insertDroppedCols,
					List **fc_resultTargetList)
{
	/* 包装函数以保持发布分支中的 ABI 兼容性 */
	return check_sql_fn_retval_ext(fc_queryTreeLists,
								   fc_rettype, fc_rettupdesc,
								   PROKIND_FUNCTION,
								   fc_insertDroppedCols,
								   fc_resultTargetList);
}

bool check_sql_fn_retval_ext(List *fc_queryTreeLists,
						Oid fc_rettype, TupleDesc fc_rettupdesc,
						char fc_prokind,
						bool fc_insertDroppedCols,
						List **fc_resultTargetList)
{
	bool		fc_is_tuple_result = false;
	Query	   *fc_parse;
	ListCell   *fc_parse_cell;
	List	   *fc_tlist;
	int			fc_tlistlen;
	bool		fc_tlist_is_modifiable;
	char		fc_fn_typtype;
	List	   *fc_upper_tlist = NIL;
	bool		fc_upper_tlist_nontrivial = false;
	ListCell   *fc_lc;

	if (fc_resultTargetList)
		*fc_resultTargetList = NIL;	/* 在 VOID 结果的情况下进行初始化 */

	
/*
	 * 如果声明为返回 VOID，我们不关心函数内部的内容。
	 * （这也适用于没有输出参数的过程。）
	 */
	if (fc_rettype == VOIDOID)
		return false;

	/*
	 * 在函数体内找到最后一个 canSetTag 查询（我们以查询节点的子列表的列表呈现）。这不一定是
	 * 最后的解析树，因为规则重写可能在用户编写的内容之后插入查询。注意它甚至可能不在最后
	 * 一个子列表中，例如如果最后一个查询重写为 DO INSTEAD NOTHING。
	 * （在这种情况下抛出错误可能并不不合理，但这是历史行为，似乎没有必要更改。）
	 */
	fc_parse = NULL;
	fc_parse_cell = NULL;
	foreach(fc_lc, fc_queryTreeLists)
	{
		List	   *fc_sublist = lfirst_node(List, fc_lc);
		ListCell   *fc_lc2;

		foreach(fc_lc2, fc_sublist)
		{
			Query	   *fc_q = lfirst_node(Query, fc_lc2);

			if (fc_q->canSetTag)
			{
				fc_parse = fc_q;
				fc_parse_cell = fc_lc2;
			}
		}
	}

	/*
	 * 如果是普通的 SELECT，它返回目标列表所指示的内容。
	 * 否则，如果是带有 RETURNING 的 INSERT/UPDATE/DELETE，它将返回
	 * 那个。否则，函数返回类型必须是 VOID。
	 *
	 * 注意：最终用 QueryReturnsTuples 替换这个测试？我们需要
	 * 一种更通用的方法来确定输出类型。此外，考虑 FETCH 或 EXECUTE 返回
	 * 可确定的行类型似乎过于危险，因为它们依赖于相对短暂的
	 * 实体。
	 */
	if (fc_parse &&
		fc_parse->commandType == CMD_SELECT)
	{
		fc_tlist = fc_parse->targetList;
		/* tlist是可修改的，除非它是在setop查询中的虚拟项 */
		fc_tlist_is_modifiable = (fc_parse->setOperations == NULL);
	}
	else if (fc_parse &&
			 (fc_parse->commandType == CMD_INSERT ||
			  fc_parse->commandType == CMD_UPDATE ||
			  fc_parse->commandType == CMD_DELETE) &&
			 fc_parse->returningList)
	{
		fc_tlist = fc_parse->returningList;
		/* returningList总是可以被修改 */
		fc_tlist_is_modifiable = true;
	}
	else
	{
		/* 空的函数主体，或最后的语句是一个实用命令 */
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("return type mismatch in function declared to return %s",
						format_type_be(fc_rettype)),
				 errdetail("Function's final statement must be SELECT or INSERT/UPDATE/DELETE RETURNING.")));
		return false;			/* 保持编译器安静 */
	}

	/*
	 * 好的，检查目标列表是否返回与声明的类型匹配的内容，
	 * 如有必要进行修改。如果可能的话，我们将任何类型转换步骤
	 * 直接插入最终语句的目标列表中。然而，这可能会影响语句的
	 * 语义 —— 例如，我们不能安全地改变一个分组列的输出类型。
	 * 在这种情况下，我们通过插入一个额外的Query层来处理强制转换，
	 * 实际上只做一个投影。
	 */

	/*
	 * 计算结果目标列表中的非垃圾条目数量。
	 */
	fc_tlistlen = ExecCleanTargetListLength(fc_tlist);

	fc_fn_typtype = get_typtype(fc_rettype);

	if (fc_fn_typtype == TYPTYPE_BASE ||
		fc_fn_typtype == TYPTYPE_DOMAIN ||
		fc_fn_typtype == TYPTYPE_ENUM ||
		fc_fn_typtype == TYPTYPE_RANGE ||
		fc_fn_typtype == TYPTYPE_MULTIRANGE)
	{
		/*
		 * 对于标量类型返回，目标列表必须恰好有一个非垃圾条目，并且
		 * 其类型必须可以强制转换为rettype。
		 */
		TargetEntry *fc_tle;

		if (fc_tlistlen != 1)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
					 errmsg("return type mismatch in function declared to return %s",
							format_type_be(fc_rettype)),
					 errdetail("Final statement must return exactly one column.")));

		/* 我们在这里假设非垃圾TLE必须在tlist中首先出现 */
		fc_tle = (TargetEntry *) linitial(fc_tlist);
		Assert(!fc_tle->resjunk);

		if (!fc_coerce_fn_result_column(fc_tle, fc_rettype, -1,
									 fc_tlist_is_modifiable,
									 &fc_upper_tlist,
									 &fc_upper_tlist_nontrivial))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
					 errmsg("return type mismatch in function declared to return %s",
							format_type_be(fc_rettype)),
					 errdetail("Actual return type is %s.",
							   format_type_be(exprType((Node *) fc_tle->expr)))));
	}
	else if (fc_fn_typtype == TYPTYPE_COMPOSITE || fc_rettype == RECORDOID)
	{
		/*
		 * 返回一个行类型。
		 *
		 * 请注意，我们不会将复合类型上的域视为“行类型”返回类型；
		 * 它会经过上面的标量情况。因为我们只提供逐列的隐式类型转换，
		 * 不会转换整个记录结果。所以产生复合结果的域的唯一方法是
		 * 将其计算为显式的单列结果。下面的单复合列代码路径可以处理
		 * 这种情况，但它不会被访问。
		 */
		int			fc_tupnatts;	/* 元组中的物理列数 */
		int			fc_tuplogcols; /* 元组中未删除列的数量 */
		int			fc_colindex;	/* 物理列索引 */

		/*
		 * 如果目标列表有一个非垃圾条目，并且该表达式具有或可以被强制
		 * 转换为声明的返回类型，则将其作为结果。这允许，例如，'SELECT
		 * func2()'，其中func2具有与调用它的函数相同的复合返回类型。
		 * 这一条款产生了一些模糊性 —— 也许这个表达式是复合结果的唯一
		 * 字段 —— 但只要我们不太过热情地发明从标量到复合类型的强制转换，
		 * 它就足够有效。
		 *
		 * XXX 请注意，如果rettype是RECORD而表达式是命名复合类型，或者
		 * 反之，这种强制转换将成功，无论记录类型是否真的匹配。
		 * 目前我们依赖运行时类型检查来捕捉任何不一致，但在解析时做得
		 * 更好会很好。
		 *
		 * 不过，对于过程来说，我们绝对不能这样做。带有输出参数的过程
		 * 的rettype是RECORD，而CALL代码期望获取与输出参数列表相对应的
		 * 结果，即使只有一个参数是复合的。
		 */
		if (fc_tlistlen == 1 && fc_prokind != PROKIND_PROCEDURE)
		{
			TargetEntry *fc_tle = (TargetEntry *) linitial(fc_tlist);

			Assert(!fc_tle->resjunk);
			if (fc_coerce_fn_result_column(fc_tle, fc_rettype, -1,
										fc_tlist_is_modifiable,
										&fc_upper_tlist,
										&fc_upper_tlist_nontrivial))
			{
				/* 请注意，我们没有设置is_tuple_result */
				goto tlist_coercion_finished;
			}
		}

		/*
		 * 如果调用者没有提供预期的tupdesc，我们不能进行任何进一步的检查。
		 * 假设我们返回整个元组。
		 */
		if (fc_rettupdesc == NULL)
		{
			/* 如果请求，返回tlist */
			if (fc_resultTargetList)
				*fc_resultTargetList = fc_tlist;
			return true;
		}

		/*
		 * 验证目标列表是否匹配返回元组类型。我们扫描非resjunk列，并在必要时
		 * 将它们强制转换以匹配未删除属性的数据类型。对于已删除属性，
		 * 如果调用者要求，插入NULL结果列。
		 */
		fc_tupnatts = fc_rettupdesc->natts;
		fc_tuplogcols = 0;			/* 我们将在处理时计算未删除的列 */
		fc_colindex = 0;

		foreach(fc_lc, fc_tlist)
		{
			TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_lc);
			Form_pg_attribute fc_attr;

			/* resjunk列可以简单地忽略 */
			if (fc_tle->resjunk)
				continue;

			do
			{
				fc_colindex++;
				if (fc_colindex > fc_tupnatts)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
							 errmsg("return type mismatch in function declared to return %s",
									format_type_be(fc_rettype)),
							 errdetail("Final statement returns too many columns.")));
				fc_attr = TupleDescAttr(fc_rettupdesc, fc_colindex - 1);
				if (fc_attr->attisdropped && fc_insertDroppedCols)
				{
					Expr	   *fc_null_expr;

					/* 我们插入的NULL的类型并不重要 */
					fc_null_expr = (Expr *) makeConst(INT4OID,
												   -1,
												   InvalidOid,
												   sizeof(int32),
												   (Datum) 0,
												   true,	/* isnull */
												   true /* byval */ );
					fc_upper_tlist = lappend(fc_upper_tlist,
										  makeTargetEntry(fc_null_expr,
														  list_length(fc_upper_tlist) + 1,
														  NULL,
														  false));
					fc_upper_tlist_nontrivial = true;
				}
			} while (fc_attr->attisdropped);
			fc_tuplogcols++;

			if (!fc_coerce_fn_result_column(fc_tle,
										 fc_attr->atttypid, fc_attr->atttypmod,
										 fc_tlist_is_modifiable,
										 &fc_upper_tlist,
										 &fc_upper_tlist_nontrivial))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("return type mismatch in function declared to return %s",
								format_type_be(fc_rettype)),
						 errdetail("Final statement returns %s instead of %s at column %d.",
								   format_type_be(exprType((Node *) fc_tle->expr)),
								   format_type_be(fc_attr->atttypid),
								   fc_tuplogcols)));
		}

		/* rettupdesc中剩余的列最好都被删除 */
		for (fc_colindex++; fc_colindex <= fc_tupnatts; fc_colindex++)
		{
			if (!TupleDescAttr(fc_rettupdesc, fc_colindex - 1)->attisdropped)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("return type mismatch in function declared to return %s",
								format_type_be(fc_rettype)),
						 errdetail("Final statement returns too few columns.")));
			if (fc_insertDroppedCols)
			{
				Expr	   *fc_null_expr;

				/* 我们插入的NULL的类型并不重要 */
				fc_null_expr = (Expr *) makeConst(INT4OID,
											   -1,
											   InvalidOid,
											   sizeof(int32),
											   (Datum) 0,
											   true,	/* isnull */
											   true /* byval */ );
				fc_upper_tlist = lappend(fc_upper_tlist,
									  makeTargetEntry(fc_null_expr,
													  list_length(fc_upper_tlist) + 1,
													  NULL,
													  false));
				fc_upper_tlist_nontrivial = true;
			}
		}

		/* 报告我们返回整个元组结果 */
		fc_is_tuple_result = true;
	}
	else
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("return type %s is not supported for SQL functions",
						format_type_be(fc_rettype))));

tlist_coercion_finished:

	/*
	 * 如有必要，通过注入一个额外的Query层来修改最终的Query，
	 * 该层仅执行投影。 （对非SELECT查询这样做是有问题的，但我们
	 * 永远不需要这样做；RETURNING列表始终可以就地修改。）
	 */
	if (fc_upper_tlist_nontrivial)
	{
		Query	   *fc_newquery;
		List	   *fc_colnames;
		RangeTblEntry *fc_rte;
		RangeTblRef *fc_rtr;

		Assert(fc_parse->commandType == CMD_SELECT);

		/* 大部分上层Query结构可以保持为零或NULL */
		fc_newquery = makeNode(Query);
		fc_newquery->commandType = CMD_SELECT;
		fc_newquery->querySource = fc_parse->querySource;
		fc_newquery->canSetTag = true;
		fc_newquery->targetList = fc_upper_tlist;

		/* 我们需要一个合理的列名列表用于子查询RTE */
		fc_colnames = NIL;
		foreach(fc_lc, fc_parse->targetList)
		{
			TargetEntry *fc_tle = (TargetEntry *) lfirst(fc_lc);

			if (fc_tle->resjunk)
				continue;
			fc_colnames = lappend(fc_colnames,
							   makeString(fc_tle->resname ? fc_tle->resname : ""));
		}

		/* 为子查询构建合适的RTE */
		fc_rte = makeNode(RangeTblEntry);
		fc_rte->rtekind = RTE_SUBQUERY;
		fc_rte->subquery = fc_parse;
		fc_rte->eref = fc_rte->alias = makeAlias("*SELECT*", fc_colnames);
		fc_rte->lateral = false;
		fc_rte->inh = false;
		fc_rte->inFromCl = true;
		fc_newquery->rtable = list_make1(fc_rte);

		fc_rtr = makeNode(RangeTblRef);
		fc_rtr->rtindex = 1;
		fc_newquery->jointree = makeFromExpr(list_make1(fc_rtr), NULL);

		/*
		 * 如果原始查询具有行安全性，请确保新查询被标记为有行安全性。
		 */
		fc_newquery->hasRowSecurity = fc_parse->hasRowSecurity;

		/* 在查询列表的正确元素中替换原始查询 */
		lfirst(fc_parse_cell) = fc_newquery;
	}

	/* 如果请求，返回tlist（可能已修改） */
	if (fc_resultTargetList)
		*fc_resultTargetList = fc_upper_tlist;

	return fc_is_tuple_result;
}

/*
 * 处理check_sql_fn_retval的一个函数结果列
 *
 * 将输出值强制转换为所需的类型/typmod，并向*upper_tlist添加一列。
 * 如果我们添加的上层tlist项不仅仅是一个Var，则将*upper_tlist_nontrivial设置为true。
 *
 * 如果可以，则返回true；如果无法强制转换为所需类型，则返回false
 * （在这种情况下，没有进行任何更改）
 */
static bool fc_coerce_fn_result_column(TargetEntry *fc_src_tle,
						Oid fc_res_type,
						int32 fc_res_typmod,
						bool fc_tlist_is_modifiable,
						List **fc_upper_tlist,
						bool *fc_upper_tlist_nontrivial)
{
	TargetEntry *fc_new_tle;
	Expr	   *fc_new_tle_expr;
	Node	   *fc_cast_result;

	
	if (fc_tlist_is_modifiable && fc_src_tle->ressortgroupref == 0)
	{
		/* 在必要时可以就地修改 src_tle */
		fc_cast_result = coerce_to_target_type(NULL,
											(Node *) fc_src_tle->expr,
											exprType((Node *) fc_src_tle->expr),
											fc_res_type, fc_res_typmod,
											COERCION_ASSIGNMENT,
											COERCE_IMPLICIT_CAST,
											-1);
		if (fc_cast_result == NULL)
			return false;
		assign_expr_collations(NULL, fc_cast_result);
		fc_src_tle->expr = (Expr *) fc_cast_result;
		/* 创建一个引用可能修改过的 TLE 的 Var */
		fc_new_tle_expr = (Expr *) makeVarFromTargetEntry(1, fc_src_tle);
	}
	else
	{
		/* 任何强制转换必须在上层 tlist 中进行 */
		Var		   *fc_var = makeVarFromTargetEntry(1, fc_src_tle);

		fc_cast_result = coerce_to_target_type(NULL,
											(Node *) fc_var,
											fc_var->vartype,
											fc_res_type, fc_res_typmod,
											COERCION_ASSIGNMENT,
											COERCE_IMPLICIT_CAST,
											-1);
		if (fc_cast_result == NULL)
			return false;
		assign_expr_collations(NULL, fc_cast_result);
		/* 强制转换是否真的有效？ */
		if (fc_cast_result != (Node *) fc_var)
			*fc_upper_tlist_nontrivial = true;
		fc_new_tle_expr = (Expr *) fc_cast_result;
	}
	fc_new_tle = makeTargetEntry(fc_new_tle_expr,
							  list_length(*fc_upper_tlist) + 1,
							  fc_src_tle->resname, false);
	*fc_upper_tlist = lappend(*fc_upper_tlist, fc_new_tle);
	return true;
}


/*
 * CreateSQLFunctionDestReceiver -- 创建一个合适的 DestReceiver 对象
 */
DestReceiver * CreateSQLFunctionDestReceiver(void)
{
	DR_sqlfunction *fc_self = (DR_sqlfunction *) palloc0(sizeof(DR_sqlfunction));

	fc_self->pub.receiveSlot = fc_sqlfunction_receive;
	fc_self->pub.rStartup = fc_sqlfunction_startup;
	fc_self->pub.rShutdown = fc_sqlfunction_shutdown;
	fc_self->pub.rDestroy = fc_sqlfunction_destroy;
	fc_self->pub.mydest = DestSQLFunction;

	/* 私有字段将在 postquel_start 中设置 */

	return (DestReceiver *) fc_self;
}

/*
 * sqlfunction_startup --- 执行器启动
 */
static void fc_sqlfunction_startup(DestReceiver *fc_self, int fc_operation, TupleDesc fc_typeinfo)
{
	/* 不执行操作 */
}

/*
 * sqlfunction_receive --- 接收一个元组
 */
static bool fc_sqlfunction_receive(TupleTableSlot *fc_slot, DestReceiver *fc_self)
{
	DR_sqlfunction *fc_myState = (DR_sqlfunction *) fc_self;

	/* 根据需要过滤元组 */
	fc_slot = ExecFilterJunk(fc_myState->filter, fc_slot);

	/* 将过滤后的元组存储到 tuplestore 中 */
	tuplestore_puttupleslot(fc_myState->tstore, fc_slot);

	return true;
}

/*
 * sqlfunction_shutdown --- 执行器结束
 */
static void fc_sqlfunction_shutdown(DestReceiver *fc_self)
{
	/* 不执行操作 */
}

/*
 * sqlfunction_destroy --- 释放 DestReceiver 对象
 */
static void fc_sqlfunction_destroy(DestReceiver *fc_self)
{
	pfree(fc_self);
}
